package org.xdq.demo.base;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
import org.xdq.demo.base.dao.TokenDao;

import java.util.Date;
import java.util.concurrent.TimeUnit;

public class TokenUtils {

    private static final String CLAIM_USERID = "UserID";
    private static final String CLAIM_USERNAME = "UserName";
    private static final long EXPIRED_TIME = 60; //单位：秒

    //获取StringRedisTemplate（专门操作String的redis操作便捷类）
    private static StringRedisTemplate stringRedisTemplate(){
        return CommonBaseConfig.applicationContext().getBean(StringRedisTemplate.class);
    }

    private static String sign(CurrentUser currentUser,String securityKey){
        Algorithm algorithm = Algorithm.HMAC256(securityKey);
        String token = JWT.create()
                .withClaim(CLAIM_USERID, currentUser.getUserId())
                .withClaim(CLAIM_USERNAME, currentUser.getUserName())
                .withExpiresAt(new Date(System.currentTimeMillis()+EXPIRED_TIME*1000))
                .sign(algorithm);
        return token;
    }

    /**
     * 根据当前用户信息和当前用户密码生成一个登录令牌
     * @param currentUser
     * @param password
     * @return
     */
    public static String loginSign(CurrentUser currentUser,String password){

        String token = sign(currentUser,password);

        //以token即为key，有为value，存入redis，过期时间为EXPIRED_TIME*2
        stringRedisTemplate().opsForValue().set(token, token, EXPIRED_TIME*2, TimeUnit.SECONDS);
        return token;
    }

    /**
     * 根据客户端传来的令牌，获取当前用户信息
     * @param clientToken
     * @return
     */
    public static CurrentUser getCurrentUser(String clientToken){

        //客户端令牌为空是否为空
        if(!StringUtils.hasText(clientToken)){
            throw new SysException("令牌为空！请登录。");
        }

        DecodedJWT decodedJWT = null;
        try {
            decodedJWT = JWT.decode(clientToken);
        } catch (JWTDecodeException e) {
            throw new BusinessException("令牌格式错误！请登录。",e);
        }
        String userId = null;
        String userName = null;
        try {
            userId = decodedJWT.getClaim(CLAIM_USERID).asString();
        } catch (Exception e) {
            throw new BusinessException("令牌解析错误！请登录。",e);
        }
        try {
            userName = decodedJWT.getClaim(CLAIM_USERNAME).asString();
        } catch (Exception e) {
            throw new BusinessException("令牌解析错误！请登录。",e);
        }

        if(!StringUtils.hasText(userId) || !StringUtils.hasText(userName)){
            throw new BusinessException("令牌中缺失用户信息！请登录。");
        }

        return new CurrentUser(userId,userName);
    }

    /**
     * 验证客户端传来的令牌的合法性，若不合法抛出异常
     * @param clientToken
     */
    public static void verify(String clientToken){
        CurrentUser currentUser = getCurrentUser(clientToken);

        //获取缓存令牌
        String cacheToken = stringRedisTemplate().opsForValue().get(clientToken);
        if(cacheToken == null){
            throw new BusinessException("令牌过期！请重新登录。");
        }


        String userId = currentUser.getUserId();

        //ApplicationContext ctx = new ClassPathXmlApplicationContext()；
        String password = null;
        try {
            TokenDao tokenDao = CommonBaseConfig.applicationContext().getBean(TokenDao.class);
            password = tokenDao.findPasswordByUserId(userId);
        } catch (Exception e) {
            throw new BusinessException("无法获取密钥！",e);
        }

        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(password)).build();
            //verifier.verify(clientToken);
            verifier.verify(cacheToken);//验证缓存令牌
        }catch(TokenExpiredException e){//token本身过期,刷新令牌
            //throw new BusinessException("令牌过期！请重新登录。");
            String newToken = sign(currentUser, password);
            stringRedisTemplate().opsForValue().set(clientToken, newToken, EXPIRED_TIME*2, TimeUnit.SECONDS);
        }catch (Exception e) {
            throw new BusinessException("令牌非法！请重新。");
        }


    }



}
